package zxsContext

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"zxs-micro-2/zxs-micro-common/codec/http_codec"
	_ "zxs-micro-2/zxs-micro-common/codec/serializer"
	"zxs-micro-2/zxs-micro-common/common"
)

const (
	CTX_MSG_KEY              = "ZXS_MICRO_CONTEXT_KEY"
	CTX_HTTP_HEAD_KEY        = "ZXS_MICRO_CONTEXT_HTTP_HEAD"
	CTX_GRPC_HEAD_KEY        = "ZXS_MICRO_CONTEXT_GRPC_HEAD"
	CTX_REQUEST_VALUE_KEY    = "ZXS_MICRO_CONTEXT_REQUEST_VALUE"
	CTX_CONTEXT_OPTION_KEY   = "ZXS_MICRO_CONTEXT_OPTION_KEY"
	CTX_CONTEXT_REQ_HEAD_KEY = "ZXS_MICRO_CONTEXT_REQ_HEAD_KEY"
)

var (
	UnknownContextError = errors.New("这是什么鬼ctx？")
)

var msgPool = sync.Pool{
	New: newContextMsg,
}

type ZxsContextMsg struct {
	ctx    context.Context
	ctxId  string
	values map[string]interface{}
	rwLock sync.RWMutex
}

type ZxsHttpHead struct {
	ReqBody  []byte
	Request  *http.Request
	Response http.ResponseWriter
}

func GenerateCtx() context.Context {
	msg := msgPool.Get().(*ZxsContextMsg)
	ctx := context.WithValue(context.Background(), CTX_MSG_KEY, msg)
	msg.ctx = ctx
	return ctx
}

func newContextMsg() interface{} {
	msg := new(ZxsContextMsg)
	msg.ctxId = common.GenerateRandomString(32)
	msg.values = make(map[string]interface{})
	return msg
}

func (msg *ZxsContextMsg) GetValue(key string) interface{} {
	msg.rwLock.RLock()
	defer msg.rwLock.RUnlock()
	return msg.values[key]
}

func (msg *ZxsContextMsg) AddValue(key string, value interface{}) {
	msg.rwLock.Lock()
	msg.values[key] = value
	msg.rwLock.Unlock()
}

func ParseToHttpContext(ctx context.Context, r *http.Request, rw http.ResponseWriter) context.Context {
	msg, ok := ctx.Value(CTX_MSG_KEY).(*ZxsContextMsg)
	if !ok {
		msg = msgPool.Get().(*ZxsContextMsg)
		msg.ctx = ctx
		ctx = context.WithValue(ctx, CTX_MSG_KEY, msg)
	}
	h := new(ZxsHttpHead)
	h.Request = r
	h.Response = rw
	var reqbody []byte
	if h.Request.Method == http.MethodGet {
		reqbody = []byte(h.Request.URL.RawQuery)
	} else {
		var exist bool
		ct := h.Request.Header.Get("Content-Type")
		for contentType, serializationType := range http_codec.ContentTypeSerializationType {
			if strings.Contains(ct, contentType) {
				msg.values["SerializationType"] = serializationType
				exist = true
				break
			}
		}
		if exist {
			var err error
			reqbody, err = getBody(ct, h.Request)
			if err != nil {
				return ctx
			}
		}
	}
	h.ReqBody = reqbody
	msg.AddValue(CTX_HTTP_HEAD_KEY, h)

	return context.WithValue(ctx, CTX_MSG_KEY, msg)
}

// getBody gets the body of request.
func getBody(contentType string, r *http.Request) ([]byte, error) {
	if strings.Contains(contentType, http_codec.SerializationTypeContentType[http_codec.SerializationTypeFormData]) {
		if r.Form == nil {
			if err := r.ParseMultipartForm(32 << 20); err != nil {
				return nil, fmt.Errorf("parse multipart form: %w", err)
			}
		}
		return []byte(r.Form.Encode()), nil
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, fmt.Errorf("body readAll: %w", err)
	}
	return body, nil
}
